/*
* Copyright (c) 2006, obsoleet industries
* All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
*     * Redistributions of source code must retain the above copyright
*       notice, this list of conditions and the following disclaimer.
*     * Redistributions in binary form must reproduce the above copyright
*       notice, this list of conditions and the following disclaimer in the
*       documentation and/or other materials provided with the distribution.
*     * Neither the name of obsoleet industries nor the names of its
*       contributors may be used to endorse or promote products derived from
*       this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/


using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;
using libsecondlife;
using libsecondlife.Packets;
using Meebey.SmartIrc4net;

namespace slirc
{
    partial class SlircNG
    {
        void InitializeCallbacks()
        {
            debug("Initializing SL callbacks");
            Client.Self.OnChat += new MainAvatar.ChatCallback(OnChatEvent);
            Client.Network.RegisterCallback(PacketType.TeleportFinish, new NetworkManager.PacketCallback(OnTeleportFinish));
            Client.Network.RegisterCallback(PacketType.RegionHandshake, new NetworkManager.PacketCallback(OnRegionHandshake));
            Client.Network.RegisterCallback(PacketType.AlertMessage, new NetworkManager.PacketCallback(OnAlertMessage));
            Client.Network.OnConnected += new NetworkManager.ConnectedCallback(OnConnectedEvent);
            Client.Network.OnSimDisconnected += new NetworkManager.SimDisconnectCallback(OnSimDisconnectEvent);
            Client.Objects.OnAvatarMoved += new ObjectManager.AvatarMovedCallback(OnAvatarMovedEvent);
            Client.Objects.OnNewAvatar += new ObjectManager.NewAvatarCallback(OnNewAvatarEvent);
            Client.Objects.OnObjectKilled += new ObjectManager.KillObjectCallback(OnObjectKilledEvent);
            Client.Self.OnInstantMessage += new MainAvatar.InstantMessageCallback(OnInstantMessageEvent);
            Client.Self.OnTeleport += new MainAvatar.TeleportCallback(OnTeleportEvent);
            Client.OnLogMessage += new SecondLife.LogCallback(Client_OnLogMessage);

        }
        void OnConnectedEvent(object sender)
        {
            debug("Connected ", 3);
            Client.Self.Status.UpdateTimer.Start();
            Client.Grid.AddEstateSims();
        }
        void OnSimDisconnectEvent(Simulator sim, NetworkManager.DisconnectType type)
        {
            debug("Disconnected from sim " + sim.Region.Name + " [m] " + type.ToString(), 2);
            //FIXME - IPEndPoint is not a valid comparison and causes an error
            if (logout || sim.IPEndPoint != Client.Network.CurrentSim.IPEndPoint) return;
            debug("Logging out", 3);
            Client.Network.Logout();
            while (Client.Network.Connected)
            {
                System.Threading.Thread.Sleep(1);
            }
            debug("Logging in", 3);
            while (!Login());
        }
        void OnAlertMessage(Packet packet, Simulator sim)
        {
            AlertMessagePacket p = (AlertMessagePacket)packet;
            debug(Helpers.FieldToString(p.AlertData.Message), 2);
        }
        void OnChatEvent(string message, byte audible, byte chatType, byte sourceType, string name, LLUUID fromAgentID, LLUUID ownerID, LLVector3 position)
        {
            if (chatType > 3 || audible < 1) return;
            if (name == firstName + " " + lastName) return;
			debug("Incoming chat from " + name + " : " + message, 5);
            if ((chatType == 1 && sourceType == 1) && (message.Substring(0, 1) == "!" || message.Substring(0, 1) == "/"))
            {
				if ( name != "[slirc]" ) {
					parseSlCommand(message, fromAgentID);
				}
            }
            else
            {
                //build message
                string sendMessage = String.Empty;
                if (!uniqueIRCNick)
                {
                    sendMessage = "<" + name + "> ";
                }
                if (chatType == 1 && sourceType == 1)
                {
                    sendMessage = sendMessage + message;
                }
                else if (chatType == 0 && sourceType == 1 && !uniqueIRCNick)
                {
                    sendMessage = sendMessage + "(whispers) " + message;
                }
                else if (chatType == 2 && sourceType == 1 && !uniqueIRCNick)
                {
                    sendMessage = sendMessage + "(shouts) " + message;
                }
                if (sourceType == 2)
                {
                    //fixme
                    return;
                }
                if (relayChat && relaySL)
                {
                    if (sourceType == 1)
                    {
                        if (uniqueIRCNick)
                        {
                            if (ircconns.ContainsKey(fromAgentID))
                            {
                                if (ircconns[fromAgentID].IsConnected && relayChat && relaySL && name != Client.Self.FirstName + " " + Client.Self.LastName)
                                {
                                    if (sendMessage.Substring(0, 2) == "/me")
                                    {
                                        ircconns[fromAgentID].SendMessage(SendType.Action, ircChannel, sendMessage);
                                    }
                                    else
                                    {
                                        ircconns[fromAgentID].SendMessage(SendType.Message, ircChannel, sendMessage);
                                    }
                                }
                            }
                        }
                        else
                        {
                            myClient.SendMessage(SendType.Message, ircChannel, sendMessage);
                        }
                    }
                }	
            }
        }
        void OnInstantMessageEvent(LLUUID fromAgentID, string fromAgentName, LLUUID toAgentID, uint parentEstateID, LLUUID regionID, LLVector3 position, byte dialog, bool groupIM, LLUUID imSessionID, DateTime timestamp, string message, byte offline, byte[] binaryBucket)
        {
            if (offline > 0) return;
            bool master = false;
            bool access = false;
            debug("SL IM from" + fromAgentName + " : " + message, 5);
            if ( fromAgentID == masterSL)
            {
                master = true;
            }
            foreach (LLUUID thisSL in allowedSL)
            {
                if (thisSL == fromAgentID )
                {
                    access = true;
                    break;
                }
            }

            //Teleport requests (dialog set to 22)
            if (dialog == (int) MainAvatar.InstantMessageDialog.RequestTeleport )
            {
                if (master || access)
                {
                    Console.WriteLine("Accepting teleport request from " + fromAgentName + " : " + message, 3);
                    Client.Self.TeleportLureRespond(fromAgentID, true);
                }
                return;
            }
            if (message.Length > 0)
            {
                if ((message.Substring(0, 1) == "!" || message.Substring(0, 1) == "/"))
                {
                    parseSlCommand(message, fromAgentID, imSessionID);
                }
            }
        }
        void OnTeleportEvent(Simulator sim, string message, MainAvatar.TeleportStatus status)
        {
            debug("Teleport (" + status.ToString() + "): " + message, 4);
        }
        void OnTeleportFinish(Packet packet, Simulator simulator)
        {
            //TeleportFinishPacket reply = (TeleportFinishPacket)packet;
            debug("Finished teleport to " + simulator.Region.Name, 3);
            Client.Self.Status.SendUpdate();
        }
        void OnRegionHandshake(Packet packet, Simulator simulator)
        {
            //RegionHandshakePacket handshake = (RegionHandshakePacket)packet;
            debug("Handshake received for region " + simulator.Region.Name, 3);
			teleportEvent.Set();
        }
        void OnAvatarMovedEvent(Simulator simulator, AvatarUpdate avatar, ulong regionHandle, ushort timeDilation)
        {
			bool check = false;
			LLUUID key = new LLUUID();
			string name = null;
			for ( int i = 0 ; i < avatars.Count ; i++ ) {
				if (avatars[i].localID == avatar.LocalID ) {
					check = true;
					key = avatars[i].key;
					name = avatars[i].name;
					avatarCache tempAvatar = avatars[i];
					tempAvatar.timestamp = Helpers.GetUnixTime();
					tempAvatar.pos = avatar.Position;
					avatars[i]  = tempAvatar;
					//thisAvatar.timestamp = Helpers.GetUnixTime();
					break;
				}
			}
			if ( !check ) { 
				debug("Received AvatarMovedEvent for avatar not in our cache", 4);
			}
			
			LLVector3 myPos = new LLVector3(0,0,0);
			if (Client.Self.SittingOn == 0)
			{
				myPos = Client.Self.Position;
			} 
			if (uniqueIRCNick && (Helpers.VecDist(avatar.Position, myPos) < 20) && (name != Client.Self.FirstName + " " + Client.Self.LastName))
			{
				if (!ircconns.ContainsKey(key))
				{
					check = ircConnect(key, name, false);
					if (!check)
					{
						debug("Unable to add " + name + " to IRC", 2);
					}
				}
			}
			else
			{
				if (ircconns.ContainsKey(key))
				{
					ircconns[key].RfcQuit(name + " is out of range");
					lock (ircconns)
					{
						ircconns.Remove(key);
					}
					lock (listenthreads)
					{
						listenthreads[key].Abort();
					}
				}
			}
        }
        void OnNewAvatarEvent(Simulator simulator, Avatar avatar, ulong regionHandle, ushort timeDilation)
        {
			foreach ( avatarCache thisAvatar in avatars ) {
				if ( thisAvatar.key == avatar.ID ) {
					return;
				}
			}
			avatarCache newAvatar;
			newAvatar.key = avatar.ID;
			newAvatar.name = avatar.Name;
			newAvatar.pos = avatar.Position;
			newAvatar.timestamp = Helpers.GetUnixTime();
			newAvatar.localID = avatar.LocalID;
			
            lock (avatars)
            {
                avatars.Add(newAvatar);
            }
            if (uniqueIRCNick && (Helpers.VecDist(avatar.Position, Client.Self.Position) < 20) && (avatar.Name != Client.Self.FirstName + " " + Client.Self.LastName))
            {
                bool check = ircConnect(avatar.ID, avatar.Name, false);
                if (!check)
                {
                    Client.Self.Chat("Unable to add " + avatar.Name + " to IRC", 0, MainAvatar.ChatType.Say);
                }
            }
        }
        void OnObjectKilledEvent(Simulator simulator, uint objectID)
        {
			avatarCache killAvatar = new avatarCache();
            foreach ( avatarCache thisAvatar in avatars ) {
				if ( thisAvatar.localID == objectID ) {
					killAvatar = thisAvatar;
					break;
				}
			}
			if ( killAvatar.name != null ) {
				lock ( avatars )
				{
					avatars.Remove(killAvatar);
				}
			}
        }
        void Client_OnLogMessage(string message, Helpers.LogLevel level)
        {
            message.Remove(message.Length - 1);
            if (level == Helpers.LogLevel.Debug)
            {
                debug(message, 5);
            }
            else if (level == Helpers.LogLevel.Error)
            {
                debug(message, 1);
            }
            else if (level == Helpers.LogLevel.Info)
            {
                //TODO : adjust levels in libsl
                debug(message, 4);
            }
            else if (level == Helpers.LogLevel.Warning)
            {
                debug(message, 2);
            }
        }

        void initIRCCallbacks()
        {
            debug("Initializing IRC callbacks", 5);
            myClient.OnChannelMessage += new IrcEventHandler(ircclient_OnChannelMessage);
            myClient.OnChannelAction += new ActionEventHandler(ircclient_OnChannelAction);
            myClient.OnDisconnected += new EventHandler(ircclient_OnDisconnected);
            myClient.OnQueryMessage += new IrcEventHandler(ircclient_OnQueryMessage);
        }
        void ircclient_OnChannelMessage(object sender, IrcEventArgs e)
        {
            if (e.Data.Message.Substring(0, 1) == "!")
            {
                debug("IRC Command from " + e.Data.Nick, 5);
                parseIrcCommand(e.Data.Message, e.Data.Ident + "@" + e.Data.Host);
                
            }
            else
            {
                bool isAvatar = false;
                if (uniqueIRCNick)
                {
                    //need to check if it's coming from an "avatar"
                    foreach (avatarCache thisAvatar in avatars)
                    {
                        string tempString = thisAvatar.name;
                        if (e.Data.Nick == tempString.Replace(" ", ""))
                        {
                            isAvatar = true;
                            break;
                        }
                    }
                }
                if (relayChat && relayIRC && !isAvatar)
                {
                    string message = null;
                    message = "<" + e.Data.Nick + ">!@#" + e.Data.Message;
                    Client.Self.Chat(message, 4312, MainAvatar.ChatType.Say);
                }
            }
        }
        void ircclient_OnChannelAction(object sender, ActionEventArgs e)
        {
            bool isAvatar = false;
            if (uniqueIRCNick)
            {
                //need to check if it's coming from an "avatar"
                foreach (avatarCache thisAvatar in avatars)
                {
                    string tempString = thisAvatar.name;
                    if (e.Data.Nick == tempString.Replace(" ", ""))
                    {
                        isAvatar = true;
                        break;
                    }
                }
                if (relayChat && relayIRC && !isAvatar)
                {
                    string message = null;
                    message = " *" + e.Data.Nick + "!@#" + e.Data.Message;
                    Client.Self.Chat(message, 4312, MainAvatar.ChatType.Say);
                }
            }
        }
        void ircclient_OnDisconnected(object sender, EventArgs e)
        {
            debug("IRC Disconnected " , 2);
            if (listenthreads.Count > 0)
            {
                foreach (KeyValuePair<LLUUID, Thread> dictEnum in listenthreads)
                {
                    dictEnum.Value.Abort();
                }
            }
            if (ircconns.Count > 0)
            {
                foreach (KeyValuePair<LLUUID, IrcClient> dictEnum in ircconns)
                {
                    dictEnum.Value.RfcQuit("relay closed");
                }
            }
			lock(listenthreads) {
				listenthreads.Clear();
			}
			lock (ircconns) {
				ircconns.Clear();
			}
            //myListenThread.Abort();
            myClient.RfcQuit("relay closed");
            bool check = false;
            debug("Reconnecting to IRC", 3);
            while (!check)
            {
                check = ircConnect(myNick);
            }
            if (uniqueIRCNick)
            {
                check = false;
                foreach (avatarCache thisAvatar in avatars)
                {
                    if ((Helpers.VecDist(thisAvatar.pos, Client.Self.Position) < 20) && (thisAvatar.name != Client.Self.FirstName + " " + Client.Self.LastName))
                    {
                        check = ircConnect(thisAvatar.key, thisAvatar.name, false);
                        if (!check)
                        {
                            debug("Unable to add " + thisAvatar.name + " to IRC", 2);
                        }
                    }
                }
            }
        }
        void ircclient_OnQueryMessage(object sender, IrcEventArgs e)
        {
            parseIrcCommand(e.Data.Message, e.Data.Ident + "@" + e.Data.Host, e.Data.Nick);
        }

        void OnCampTimer(object state)
        {
            if (currentCamp == null) return;
            campPosition thisCamp = getCamp(currentCamp);
            if (thisCamp.name == null) return;
            if (thisCamp.sim.ToLower() != Client.Network.CurrentSim.Region.Name.ToLower())
            {
                debug("We seem to have drifted sims while camping. Are in " + Client.Network.CurrentSim.Region.Name + " and should be in " + thisCamp.sim, 2);
                bool check = camp(currentCamp);
                if (check)
                {
                    debug("Succesfully camped again at " + currentCamp, 3);
                }
                else
                {
                    debug("Failed to camp at " + currentCamp + ".", 3);
                    debug("Ceasing camping activity.", 2);
                    campCheckTimer.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
                    currentCamp = null;
                    commandStand();
                }
            }
        }
		
		void AvatarCacheTimerHandler(object state)
		{
			List<avatarCache> dumpAvatars = new List<avatarCache>();
			foreach ( avatarCache thisAvatar in avatars ) { 
				if ( (Helpers.GetUnixTime() - thisAvatar.timestamp) > 300 ) {
					dumpAvatars.Add(thisAvatar);
				}
			}
			lock(avatars) {
				foreach ( avatarCache thisAvatar in dumpAvatars ) {
					debug("Removing expired avatar " + thisAvatar.name + " from cache.", 4);
					avatars.Remove(thisAvatar);
				}
			}
		}
    }
}
